﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Printing;
using System.Windows;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using Oxage.Common;
using HorizontalAlignment = System.Windows.HorizontalAlignment;
using MessageBox = System.Windows.MessageBox;
using Microsoft.Win32;

namespace Oxage.Printing.Models
{
	public class PrintViewModel : ModelBase
	{
		#region Private members
		private const int dpi = 96;
		private const double factor = 3.7795; //factor = 96 / 25.4; //dpi div inch-to-mm
		private string documentPath = null;
		private FrameworkElement original = null;
		private IContractService contractService = null;
		protected delegate void CrossThreadDelegate();
		#endregion

		#region Constructor
		public PrintViewModel()
		{
			Clear();
		}
		#endregion

		#region Public properties
		/// <summary>
		/// Gets a value indicating whether debugging is enabled.
		/// </summary>
		public bool Debug
		{
			get
			{
				return Settings.Debug;
			}
		}

		/// <summary>
		/// Gets or sets a debug message.
		/// </summary>
		public string DebugMessage
		{
			get
			{
				return this.debugMessage;
			}
			set
			{
				if (this.debugMessage != value)
				{
					this.debugMessage = value;
					OnPropertyChanged("DebugMessage");
				}
			}
		}
		private string debugMessage;

		/// <summary>
		/// Gets or sets top margin in pixels.
		/// </summary>
		public double MarginTop
		{
			get
			{
				return this.marginTop;
			}
			set
			{
				if (this.marginTop != value)
				{
					this.marginTop = value;
					Settings.MarginTop = value;
					OnPropertyChanged("MarginTop");
					OnPropertyChanged("Margin");
					OnPropertyChanged("PreviewMargin");
				}
			}
		}
		private double marginTop;

		/// <summary>
		/// Gets or sets bottom margin in pixels.
		/// </summary>
		public double MarginBottom
		{
			get
			{
				return this.marginBottom;
			}
			set
			{
				if (this.marginBottom != value)
				{
					this.marginBottom = value;
					Settings.MarginBottom = value;
					OnPropertyChanged("MarginBottom");
					OnPropertyChanged("Margin");
					OnPropertyChanged("PreviewMargin");
				}
			}
		}
		private double marginBottom;

		/// <summary>
		/// Gets or sets left margin in pixels.
		/// </summary>
		public double MarginLeft
		{
			get
			{
				return this.marginLeft;
			}
			set
			{
				if (this.marginLeft != value)
				{					
					this.marginLeft = value;
					Settings.MarginLeft = value;
					OnPropertyChanged("MarginLeft");
					OnPropertyChanged("Margin");
					OnPropertyChanged("PreviewMargin");
				}
			}
		}
		private double marginLeft;

		/// <summary>
		/// Gets or sets right margin in pixels.
		/// </summary>
		public double MarginRight
		{
			get
			{
				return this.marginRight;
			}
			set
			{
				if (this.marginRight != value)
				{
					this.marginRight = value;
					Settings.MarginRight = value;
					OnPropertyChanged("MarginRight");
					OnPropertyChanged("Margin");
					OnPropertyChanged("PreviewMargin");
				}
			}
		}
		private double marginRight;

		/// <summary>
		/// Gets or sets a margin in pixels.
		/// </summary>
		/// <remarks>
		/// Margin is content edge offset from paper edge.
		/// </remarks>
		public Thickness Margin
		{
			get
			{
				return new Thickness(this.MarginLeft, this.MarginTop, this.MarginRight, this.MarginBottom);
			}
		}

		/// <summary>
		/// Gets or sets paper orientation: portrait or landscape.
		/// </summary>
		public PaperOrientations Orientation
		{
			get
			{
				return this.orientation;
			}
			set
			{
				if (this.orientation != value)
				{
					this.orientation = value;
					Settings.PaperOrientation = value;
					OnPropertyChanged("Orientation");
					OnPropertyChanged("ActualPaperWidth");
					OnPropertyChanged("ActualPaperHeight");
					OnPropertyChanged("PreviewPaperWidth");
					OnPropertyChanged("PreviewPaperHeight");
				}
			}
		}
		private PaperOrientations orientation;

		/// <summary>
		/// Gets or sets content stretching mode.
		/// </summary>
		public Stretch StretchType
		{
			get
			{
				return this.stretch;
			}
			set
			{
				if (this.stretch != value)
				{
					this.stretch = value;
					Settings.Stretch = value;
					OnPropertyChanged("StretchType");

					if (this.original != null)
					{
						Load(this.original);
					}
				}
			}
		}
		private Stretch stretch;

		public List<Stretch> StretchList
		{
			get
			{
				return this.stretchList;
			}
			set
			{
				if (this.stretchList != value)
				{
					this.stretchList = value;
					OnPropertyChanged("StretchList");
				}
			}
		}
		private List<Stretch> stretchList;

		public VerticalAlignment VerticalAlignmentType
		{
			get
			{
				return this.verticalAlignment;
			}
			set
			{
				if (this.verticalAlignment != value)
				{
					this.verticalAlignment = value;
					Settings.VerticalAlignment = value;
					if (this.PreviewBrush != null)
					{
						this.PreviewBrush.AlignmentY = this.VerticalAlignmentY;
					}
					OnPropertyChanged("VerticalAlignmentType");
					OnPropertyChanged("VerticalAlignmentY");
				}
			}
		}
		private VerticalAlignment verticalAlignment;

		public List<VerticalAlignment> VerticalAlignmentList
		{
			get
			{
				return this.verticalAlignmentList;
			}
			set
			{
				if (this.verticalAlignmentList != value)
				{
					this.verticalAlignmentList = value;
					OnPropertyChanged("VerticalAlignmentList");
				}
			}
		}
		private List<VerticalAlignment> verticalAlignmentList;

		public HorizontalAlignment HorizontalAlignmentType
		{
			get
			{
				return this.horizontalAlignment;
			}
			set
			{
				if (this.horizontalAlignment != value)
				{
					this.horizontalAlignment = value;
					Settings.HorizontalAlignment = value;
					if (this.PreviewBrush != null)
					{
						this.PreviewBrush.AlignmentX = this.HorizontalAlignmentX;
					}
					OnPropertyChanged("HorizontalAlignmentType");
					OnPropertyChanged("HorizontalAlignmentX");
				}
			}
		}
		private HorizontalAlignment horizontalAlignment;

		public List<HorizontalAlignment> HorizontalAlignmentList
		{
			get
			{
				return this.horizontalAlignmentList;
			}
			set
			{
				if (this.horizontalAlignmentList != value)
				{
					this.horizontalAlignmentList = value;
					OnPropertyChanged("HorizontalAlignmentList");
				}
			}
		}
		private List<HorizontalAlignment> horizontalAlignmentList;

		public AlignmentX HorizontalAlignmentX
		{
			get
			{
				switch (this.HorizontalAlignmentType)
				{
					case HorizontalAlignment.Left:
						return AlignmentX.Left;
						break;

					case HorizontalAlignment.Right:
						return AlignmentX.Right;
						break;

					default:
					case HorizontalAlignment.Center:
					case HorizontalAlignment.Stretch:
						return AlignmentX.Center;
						break;
				}
			}
		}

		public AlignmentY VerticalAlignmentY
		{
			get
			{
				switch (this.VerticalAlignmentType)
				{
					case VerticalAlignment.Top:
						return AlignmentY.Top;
						break;

					case VerticalAlignment.Bottom:
						return AlignmentY.Bottom;
						break;

					default:
					case VerticalAlignment.Center:
					case VerticalAlignment.Stretch:
						return AlignmentY.Center;
						break;
				}
			}
		}

		public double ActualPaperWidth
		{
			get
			{
				return this.Orientation == PaperOrientations.Portrait ? this.PaperWidth : this.PaperHeight;
			}
		}

		public double ActualPaperHeight
		{
			get
			{
				return this.Orientation == PaperOrientations.Portrait ? this.PaperHeight : this.PaperWidth;
			}
		}

		public double PaperWidth
		{
			get
			{
				return this.paperWidth;
			}
			set
			{
				if (this.paperWidth != value)
				{
					this.paperWidth = value;
					Settings.PaperWidth = value;
					OnPropertyChanged("PaperWidth");
					OnPropertyChanged("ActualPaperWidth");
					OnPropertyChanged("PreviewPaperWidth");
					FindMatchingPaperFormat();
				}
			}
		}
		private double paperWidth;

		public double PaperHeight
		{
			get
			{
				return this.paperHeight;
			}
			set
			{
				if (this.paperHeight != value)
				{
					this.paperHeight = value;
					Settings.PaperHeight = value;
					OnPropertyChanged("PaperHeight");
					OnPropertyChanged("ActualPaperHeight");
					OnPropertyChanged("PreviewPaperHeight");
					FindMatchingPaperFormat();
				}
			}
		}
		private double paperHeight;

		public PaperFormat PaperFormat
		{
			get
			{
				return this.paperFormat;
			}
			set
			{
				if (this.paperFormat != value)
				{
					if (value != null && !value.IsCustom)
					{
						this.PaperWidth = value.Width;
						this.PaperHeight = value.Height;
					}

					this.paperFormat = value;
					OnPropertyChanged("PaperFormat");
					OnPropertyChanged("ActualPaperWidth");
					OnPropertyChanged("ActualPaperHeight");
					OnPropertyChanged("PreviewPaperWidth");
					OnPropertyChanged("PreviewPaperHeight");
				}
			}
		}
		private PaperFormat paperFormat;

		public List<PaperFormat> PaperFormatList
		{
			get
			{
				return this.paperFormatList;
			}
			set
			{
				if (this.paperFormatList != value)
				{
					this.paperFormatList = value;
					OnPropertyChanged("PaperFormatList");
				}
			}
		}
		private List<PaperFormat> paperFormatList;

		public Thickness PreviewMargin
		{
			get
			{
				return new Thickness(this.MarginLeft * factor, this.MarginTop * factor, this.MarginRight * factor, this.MarginBottom * factor);
			}
		}

		public VisualBrush PreviewBrush
		{
			get
			{
				return this.previewBrush;
			}
			set
			{
				if (this.previewBrush != value)
				{
					this.previewBrush = value;
					OnPropertyChanged("PreviewBrush");
					OnPropertyChanged("CanPrint");
				}
			}
		}
		private VisualBrush previewBrush;

		public double PreviewPaperWidth
		{
			get
			{
				return (this.Orientation == PaperOrientations.Portrait ? this.PaperWidth : this.PaperHeight) * factor;
			}
		}

		public double PreviewPaperHeight
		{
			get
			{
				return (this.Orientation == PaperOrientations.Portrait ? this.PaperHeight : this.PaperWidth) * factor;
			}
		}

		public ICommand LoadCommand
		{
			get
			{
				return this.loadCommand;
			}
			set
			{
				if (this.loadCommand != value)
				{
					this.loadCommand = value;
					OnPropertyChanged("LoadCommand");
				}
			}
		}
		private ICommand loadCommand;

		public ICommand PrintCommand
		{
			get
			{
				return this.printCommand;
			}
			set
			{
				if (this.printCommand != value)
				{
					this.printCommand = value;
					OnPropertyChanged("PrintCommand");
				}
			}
		}
		private ICommand printCommand;

		public ICommand DebugCommand
		{
			get
			{
				return this.debugCommand;
			}
			set
			{
				if (this.debugCommand != value)
				{
					this.debugCommand = value;
					OnPropertyChanged("DebugCommand");
				}
			}
		}
		private ICommand debugCommand;

		public ICommand RefreshCommand
		{
			get
			{
				return this.refreshCommand;
			}
			set
			{
				if (this.refreshCommand != value)
				{
					this.refreshCommand = value;
					OnPropertyChanged("RefreshCommand");
				}
			}
		}
		private ICommand refreshCommand;

		public ICommand HyperlinkCommand
		{
			get
			{
				return this.hyperlinkCommand;
			}
			set
			{
				if (this.hyperlinkCommand != value)
				{
					this.hyperlinkCommand = value;
					OnPropertyChanged("HyperlinkCommand");
				}
			}
		}
		private ICommand hyperlinkCommand;

		/// <summary>
		/// Gets a value indicating whether printing can be performed.
		/// </summary>
		public bool CanPrint
		{
			get
			{
				return this.PreviewBrush != null;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the module is in development mode.
		/// </summary>
		public bool IsDebug
		{
			get
			{
#if DEBUG
				return true;
#else
				return false;
#endif
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the module is hosted in a stand-alone application (i.e. not as Expression Blend plugin).
		/// </summary>
		public bool IsStandalone
		{
			get
			{
				return this.isStandalone;
			}
			set
			{
				if (this.isStandalone != value)
				{
					this.isStandalone = value;
					OnPropertyChanged("IsStandalone");
				}
			}
		}
		private bool isStandalone;

		public Exception LastError
		{
			get
			{
				return this.lastError;
			}
			protected set
			{
				if (this.lastError != value)
				{
					this.lastError = value;
					OnPropertyChanged("LastError");
					OnPropertyChanged("HasError");
				}
			}
		}
		private Exception lastError;

		public bool HasError
		{
			get
			{
				return this.LastError != null;
			}
		}
		#endregion

		#region Public methods
		public void Clear()
		{
			this.LastError = null;

			//Reference: http://en.wikipedia.org/wiki/Paper_size
			this.PaperFormatList = new List<PaperFormat>();
			this.PaperFormatList.Add(new PaperFormat() { Name = "A0", Width = 841, Height = 1189 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "A1", Width = 594, Height = 841 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "A2", Width = 420, Height = 594 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "A3", Width = 297, Height = 420 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "A4", Width = 210, Height = 297 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "A5", Width = 148, Height = 210 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "A6", Width = 105, Height = 148 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "A7", Width = 74, Height = 105 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "Letter", Width = 216, Height = 279 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "Legal", Width = 216, Height = 356 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "Ledger/Tabloid", Width = 279, Height = 432 });
			this.PaperFormatList.Add(new PaperFormat() { Name = "Custom", Width = 0, Height = 0, IsCustom = true });

			this.StretchList = new List<Stretch>();
			this.StretchList.Add(Stretch.None);
			this.StretchList.Add(Stretch.Fill);
			this.StretchList.Add(Stretch.Uniform);
			this.StretchList.Add(Stretch.UniformToFill);

			this.VerticalAlignmentList = new List<VerticalAlignment>();
			this.VerticalAlignmentList.Add(VerticalAlignment.Top);
			this.VerticalAlignmentList.Add(VerticalAlignment.Center);
			this.VerticalAlignmentList.Add(VerticalAlignment.Bottom);
			//this.VerticalAlignmentList.Add(VerticalAlignment.Stretch);

			this.HorizontalAlignmentList = new List<HorizontalAlignment>();
			this.HorizontalAlignmentList.Add(HorizontalAlignment.Left);
			this.HorizontalAlignmentList.Add(HorizontalAlignment.Center);
			this.HorizontalAlignmentList.Add(HorizontalAlignment.Right);
			//this.HorizontalAlignmentList.Add(HorizontalAlignment.Stretch);

			this.MarginTop = Settings.MarginTop;
			this.MarginBottom = Settings.MarginBottom;
			this.MarginLeft = Settings.MarginLeft;
			this.MarginRight = Settings.MarginRight;
			this.StretchType = Settings.Stretch;
			this.Orientation = Settings.PaperOrientation;
			this.VerticalAlignmentType = Settings.VerticalAlignment;
			this.HorizontalAlignmentType = Settings.HorizontalAlignment;
			//this.PaperFormat = this.PapemrFormatList.FirstOrDefault(x => x.Name == "A4");
			this.PaperWidth = Settings.PaperWidth;
			this.PaperHeight = Settings.PaperHeight;
			this.LoadCommand = new LoadCommand(this);
			this.PrintCommand = new PrintCommand(this);
			this.DebugCommand = new DebugCommand(this);
			this.RefreshCommand = new RefreshCommand(this);
			this.HyperlinkCommand = new HyperlinkCommand(this);

			if (Settings.Debug)
			{
				this.DebugMessage = "Successfully initialized.";
			}
		}

		/// <summary>
		/// Loads a xaml document from a local disk.
		/// </summary>
		/// <param name="path">Full path to a xaml document.</param>
		public void Load(string path)
		{
			try
			{
				using (var fs = File.OpenRead(path))
				{
					Load(XamlReader.Load(fs) as FrameworkElement);
				}

				this.documentPath = path;
			}
			catch (Exception ex)
			{
				ReportError(ex);
			}
		}

		/// <summary>
		/// Loads a xaml element.
		/// </summary>
		/// <param name="element"></param>
		public void Load(FrameworkElement element)
		{
			if (element != null)
			{
				//Create brush for a preview
				VisualBrush brush = new VisualBrush(element);
				brush.AlignmentX = this.HorizontalAlignmentX;
				brush.AlignmentY = this.VerticalAlignmentY;
				brush.AutoLayoutContent = true;
				brush.Stretch = this.StretchType;
				brush.TileMode = TileMode.None;
				this.PreviewBrush = brush;

				//Save original for later user
				this.original = element;
			}
		}

		/// <summary>
		/// Opens a file browse dialog and loads a xaml document if user confirms.
		/// </summary>
		public void BrowseAndLoad()
		{
			OpenFileDialog dialog = new OpenFileDialog();
			dialog.Filter = "*.xaml|*.xaml";
			dialog.Multiselect = false;

			if (dialog.ShowDialog() == true)
			{
				Load(dialog.FileName);
			}
		}

		/// <summary>
		/// Opens a preview window, for debugging purposes.
		/// </summary>
		public void DebugWindow()
		{
			if (this.CanPrint)
			{
				XamlPrinter p = new XamlPrinter();
				p.Margin = this.Margin;
				p.Stretch = this.StretchType;
				p.Width = (int)this.ActualPaperWidth;
				p.Height = (int)this.ActualPaperHeight;
				p.Orientation = (this.Orientation == PaperOrientations.Landscape ? PageOrientation.Landscape : PageOrientation.Portrait);

				Window window = new Window();
				window.Content = p.GetFlowDocument(this.PreviewBrush);
				window.Show();
			}
		}

		/// <summary>
		/// Opens a print dialog and prints the document if user confirms.
		/// </summary>
		public void Print()
		{
			try
			{
				//Description is needed for system's printing queue window
				string description = (!string.IsNullOrEmpty(this.documentPath) ? Path.GetFileName(this.documentPath) : "xbprint - xaml print");

				//Show dialog and start printing
				XamlPrinter p = new XamlPrinter();
				p.Margin = this.Margin;
				p.Stretch = this.StretchType;
				p.Width = (int)this.PaperWidth;
				p.Height = (int)this.PaperHeight;
				p.Orientation = (this.Orientation == PaperOrientations.Landscape ? PageOrientation.Landscape : PageOrientation.Portrait);
				p.Print(this.PreviewBrush, description, true);

				//Info message
				WriteLine("Printing " + description);
				WriteLine(string.Format("Margin: left={0}, top={1}, right={2}, bottom={3}", this.Margin.Left, this.Margin.Top, this.Margin.Right, this.Margin.Bottom));
				WriteLine(string.Format("Size: width={0}mm, height={1}mm", this.PaperWidth, this.PaperHeight));
			}
			catch (Exception ex)
			{
				ReportError(ex);
			}
		}

		public void RefreshPreview()
		{
			Load(this.original);
		}

		/// <summary>
		/// Loads a document that has been opened in Expression Blend.
		/// </summary>
		/// <param name="contractService"></param>
		public void RegisterContractService(IContractService contractService)
		{
			this.contractService = contractService;
			this.contractService.DebugMessage += (sender, e) =>
			{
				System.Windows.Application.Current.Dispatcher.BeginInvoke(new CrossThreadDelegate(() =>
				{
					this.DebugMessage = e.Result;
				}));
			};
			this.contractService.DocumentChanged += (sender, e) =>
			{
				RefreshDocument();
			};
			RefreshDocument();
		}

		protected void RefreshDocument()
		{
			if (this.contractService.DocumentContent != null)
			{
				this.documentPath = this.contractService.DocumentPath;
				Load(this.contractService.DocumentContent);

				if (this.contractService.ProjectType == ProjectTypes.Silverlight)
				{
					this.LastError = new Exception("Silverlight project type is not supported. Try WPF.");
				}
				else if (this.contractService.ProjectType == ProjectTypes.Silverlight)
				{
					this.LastError = new Exception("This project type may not be supported. Try WPF.");
				}
				else
				{
					this.LastError = null;
				}
			}
		}
		#endregion

		#region Protected methods
		/// <summary>
		/// Selects appropriate item in a paper format combobox according to the defined paper width and height.
		/// </summary>
		protected void FindMatchingPaperFormat()
		{
			var format = this.PaperFormatList.FirstOrDefault(x => x.Width == this.PaperWidth && x.Height == this.PaperHeight)
								?? this.PaperFormatList.FirstOrDefault(x => x.IsCustom);

			if (format != null && format != this.PaperFormat)
			{
				this.PaperFormat = format;
			}
		}

		/// <summary>
		/// Reports an error message.
		/// </summary>
		/// <param name="error"></param>
		protected void ReportError(Exception error)
		{
			this.LastError = error;

			if (this.IsStandalone && error != null)
			{
				MessageBox.Show(error.Message, "Error", MessageBoxButton.OK);
			}
		}

		/// <summary>
		/// Writes a message to Expression Blend's logging window.
		/// </summary>
		/// <param name="message"></param>
		protected void WriteLine(string message)
		{
			if (this.contractService != null)
			{
				this.contractService.WriteLine(message);
			}
		}
		#endregion
	}
}
